"""Find all files under TARGETDIR that are not under SOURCEDIR."""

import argparse
from functools import partial
import hashlib
import itertools
import os
import sys


def log(message, *args, **kwargs):
    """Print a log message to stderr prefixed with the executable name."""
    print(sys.argv[0] + ':', message.format(*args, **kwargs), file=sys.stderr)


def listfiles(top, log=None):
    """Return an iterator over all regular files in *top* dir.

    If *log* is given, it is called once for each directory visited
    with two keyword arguments:

    dirpath
        the path to the visited directory
    numfiles
        the number of files in the visited directory

    Symbolic links are not followed.

    """
    def listdir(walkentry):
        dirpath, _, filenames = walkentry
        if log:
            log(numfiles=len(filenames), dirpath=dirpath)
        return (os.path.join(dirpath, filename) for filename in filenames)

    return itertools.chain.from_iterable(map(listdir, os.walk(top)))


def md5sum(filename):
    """Return the md5 sum for a given file."""
    with open(filename, 'rb') as stream:
        return hashlib.md5(stream.read()).digest()


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        'sourcedir', metavar='SOURCEDIR',
        help="a source directory",
    )
    parser.add_argument(
        'targetdir', metavar='TARGETDIR',
        help="a target directory to search for files not in SOURCEDIR",
    )
    parser.add_argument(
        '-v', '--verbose',
        dest='log',
        action='store_const',
        const=log,
        default=lambda *args, **kwargs: None,
        help="turn on verbose output to stderr",
    )
    args = parser.parse_args()

    args.log("Indexing files in {}", args.sourcedir)
    sourcelog = partial(args.log, "Hashing {numfiles} files in {dirpath}")
    sources = {
        md5sum(filename) for filename in listfiles(args.sourcedir, sourcelog)
        if not os.path.islink(filename) and os.path.isfile(filename)
    }
    args.log("Scaning {} for unique files", args.targetdir)
    targetlog = partial(args.log, "Checking {numfiles} files in {dirpath}")
    targets = (
        filename for filename in listfiles(args.targetdir, targetlog)
        if md5sum(filename) not in sources
    )
    for filename in targets:
        print(filename)
